<html>
 <head>
  <meta charset="UTF-8">
 </head>
 <body>
  <h1 data-lake-id="jZ1oD" id="jZ1oD"><span data-lake-id="ud357b793" id="ud357b793">典型回答</span></h1>
  <p data-lake-id="u19264efc" id="u19264efc"><br></p>
  <p data-lake-id="u3f076417" id="u3f076417"><br></p>
  <p data-lake-id="u9a2488dc" id="u9a2488dc"><span data-lake-id="u4d777599" id="u4d777599">关于这个问题，网上有很多种说法，甚至我看过某国内非常知名的付费专栏中，关于这个点也并不是讲解的特别清楚。那么，这里我们先总结一下Tomcat的类加载机制，然后再来证明为啥我这么说：</span></p>
  <p data-lake-id="uecce5f20" id="uecce5f20"><strong><span data-lake-id="uac35c332" id="uac35c332" style="color: #DF2A3F">​</span></strong><br></p>
  <p data-lake-id="ud5c2aa5f" id="ud5c2aa5f"><strong><span data-lake-id="u950368c5" id="u950368c5" style="color: #F38F39">Tomcat的类加载机制，在默认情况下，是先把当前要加载的类委托给BootstrapClassLoader尝试加载，为了避免JRE中的核心类被我们应用自己给覆盖（如String等），Bootstrap如果无法加载，那么就由WebAppClassLoader尝试加载，如果无法加载，那么再委托通过双亲委派的方式向上委派给Common、System等类加载进行加载，即顺序为：</span></strong><strong><span data-lake-id="ua10783d7" id="ua10783d7" class="lake-fontsize-11" style="color: #F38F39">Bootstrap-&gt;WebApp-&gt;System-&gt;Common</span></strong></p>
  <p data-lake-id="u8dfb5f26" id="u8dfb5f26"><strong><span data-lake-id="u34345066" id="u34345066" style="color: #F38F39">​</span></strong><br></p>
  <p data-lake-id="ue979864e" id="ue979864e"><strong><span data-lake-id="u9e43a56c" id="u9e43a56c" style="color: #F38F39">上面的是默认情况，tomcat中有一个配置</span></strong><code data-lake-id="u506ccaf6" id="u506ccaf6"><strong><span data-lake-id="ua8a7b609" id="ua8a7b609" style="color: #F38F39">delegate</span></strong></code><strong><span data-lake-id="u2c272ab8" id="u2c272ab8" style="color: #F38F39">，他的默认值是false，如果设置成true了，那么他就会严格遵守双亲委派，按照</span></strong><strong><span data-lake-id="uf58209ab" id="uf58209ab" class="lake-fontsize-11" style="color: #F38F39">Bootstrap-&gt;System-&gt;Common-&gt;WebApp的顺序进行加载。</span></strong></p>
  <p data-lake-id="u19389fc9" id="u19389fc9"><span data-lake-id="u2f3e5fe6" id="u2f3e5fe6" class="lake-fontsize-12" style="color: rgb(51, 51, 51)">​</span><br></p>
  <p data-lake-id="ud9707926" id="ud9707926"><span data-lake-id="ufeeb51b0" id="ufeeb51b0" class="lake-fontsize-12" style="color: rgb(51, 51, 51)">talk is cheap，show me the code</span></p>
  <p data-lake-id="ucfc77fb7" id="ucfc77fb7"><span data-lake-id="u1ffa3f7a" id="u1ffa3f7a" class="lake-fontsize-12" style="color: rgb(51, 51, 51)">​</span><br></p>
  <p data-lake-id="u6eae58eb" id="u6eae58eb"><span data-lake-id="u1b59526d" id="u1b59526d">以下是tomcat中WebappClassLoaderBase.java中loadClass的代码，我做了一些精简，并加了一些注释：</span></p>
  <p data-lake-id="udcfd1513" id="udcfd1513"><span data-lake-id="u6f928cba" id="u6f928cba">​</span><br></p>
  <pre lang="java"><code>
public Class&lt;?&gt; loadClass(String name, boolean resolve) throws ClassNotFoundException {

    //加锁，防止并发
    synchronized (JreCompat.isGraalAvailable() ? this : getClassLoadingLock(name)) {
        if (log.isDebugEnabled()) {
            log.debug("loadClass(" + name + ", " + resolve + ")");
        }
        Class&lt;?&gt; clazz = null;

        // ...

        // 检查本地缓存是否已加载该类，如果是，则直接返回缓存中的 Class 对象。
        clazz = findLoadedClass0(name);
        if (clazz != null) {
            if (log.isDebugEnabled()) {
                log.debug("  Returning class from cache");
            }
            if (resolve) {
                resolveClass(clazz);
            }
            return clazz;
        }

        // 检查另一个类加载缓存，如果是GraalVM环境，直接返回缓存中的 Class 对象。
        clazz = JreCompat.isGraalAvailable() ? null : findLoadedClass(name);
        if (clazz != null) {
            if (log.isDebugEnabled()) {
                log.debug("  Returning class from cache");
            }
            if (resolve) {
                resolveClass(clazz);
            }
            return clazz;
        }

        /*
         * 尝试使用Bootstrap类加载器加载类，以防止Web应用程序覆盖Java SE类。如果加载成功，则返回加载的 Class 对象。
         */
        String resourceName = binaryNameToPath(name, false);

        ClassLoader javaseLoader = getJavaseClassLoader();
        boolean tryLoadingFromJavaseLoader;
        try {

            URL url = javaseLoader.getResource(resourceName);
            tryLoadingFromJavaseLoader = url != null;
        } catch (Throwable t) {
            ExceptionUtils.handleThrowable(t);
            tryLoadingFromJavaseLoader = true;
        }

        if (tryLoadingFromJavaseLoader) {
            try {
                clazz = javaseLoader.loadClass(name);
                if (clazz != null) {
                    if (resolve) {
                        resolveClass(clazz);
                    }
                    return clazz;
                }
            } catch (ClassNotFoundException e) {
                // Ignore
            }
        }

        boolean delegateLoad = delegate || filter(name, true);

        // 根据 delegate 属性和其他条件判断是否应该委派加载给父类加载器。
        // 如果需要委派，则直接先进行委派
        if (delegateLoad) {
            if (log.isDebugEnabled()) {
                log.debug("  Delegating to parent classloader1 " + parent);
            }
            try {
                clazz = Class.forName(name, false, parent);
                if (clazz != null) {
                    if (log.isDebugEnabled()) {
                        log.debug("  Loading class from parent");
                    }
                    if (resolve) {
                        resolveClass(clazz);
                    }
                    return clazz;
                }
            } catch (ClassNotFoundException e) {
                // Ignore
            }
        }

        // 自己尝试加载
        // 能走到这里，肯定是BootStrap没加载到，之后还有两种情况：
        // 1、如果delegate为ture的话，说明上层类加载器也没记载到。
        // 2、如果delegate为false，那么就还没有进行过委派，先在这里尝试自己加载。
        if (log.isDebugEnabled()) {
            log.debug("  Searching local repositories");
        }
        try {
            clazz = findClass(name);
            if (clazz != null) {
                if (log.isDebugEnabled()) {
                    log.debug("  Loading class from local repository");
                }
                if (resolve) {
                    resolveClass(clazz);
                }
                return clazz;
            }
        } catch (ClassNotFoundException e) {
            // Ignore
        }

        // 如果delegate为false,说明还没有做过委派，那么委派给父类加载器加载类。
        if (!delegateLoad) {
            if (log.isDebugEnabled()) {
                log.debug("  Delegating to parent classloader at end: " + parent);
            }
            try {
                clazz = Class.forName(name, false, parent);
                if (clazz != null) {
                    if (log.isDebugEnabled()) {
                        log.debug("  Loading class from parent");
                    }
                    if (resolve) {
                        resolveClass(clazz);
                    }
                    return clazz;
                }
            } catch (ClassNotFoundException e) {
                // Ignore
            }
        }
    }

    throw new ClassNotFoundException(name);
}

</code></pre>
  <p data-lake-id="u4d06b842" id="u4d06b842"><br></p>
  <p data-lake-id="uf1ca6a8d" id="uf1ca6a8d"><span data-lake-id="ua1fc57f8" id="ua1fc57f8">整个代码的过程就是：</span></p>
  <p data-lake-id="u439d1163" id="u439d1163"><span data-lake-id="ub4eaff58" id="ub4eaff58">​</span><br></p>
  <ol list="ubba7902c">
   <li fid="uc65f7349" data-lake-id="ud663e4ad" id="ud663e4ad" data-lake-index-type="true"><span data-lake-id="u7aa963f1" id="u7aa963f1">加锁： 方法使用同步块确保线程安全</span></li>
   <li fid="uc65f7349" data-lake-id="u73f58df6" id="u73f58df6" data-lake-index-type="true"><span data-lake-id="ue55f0427" id="ue55f0427">检查已加载类缓存：</span><span data-lake-id="ue269e5c3" id="ue269e5c3"> 首先，通过调用 </span><span data-lake-id="u4cb4ce0c" id="u4cb4ce0c">findLoadedClass0</span><span data-lake-id="u48a0c481" id="u48a0c481"> 方法检查本地缓存是否已加载该类，如果是，则直接返回缓存中的 </span><span data-lake-id="ufcd83f60" id="ufcd83f60">Class</span><span data-lake-id="ue0ae7901" id="ue0ae7901"> 对象。</span></li>
   <li fid="uc65f7349" data-lake-id="u618e6f8a" id="u618e6f8a" data-lake-index-type="true"><span data-lake-id="ud5c546da" id="ud5c546da">检查已加载类缓存（GraalVM 兼容性处理）：</span><span data-lake-id="u9fac0032" id="u9fac0032"> 通过调用 </span><span data-lake-id="u9ba8025c" id="u9ba8025c">findLoadedClass</span><span data-lake-id="u733c29b7" id="u733c29b7"> 方法检查另一个类加载缓存，如果是GraalVM环境，直接返回缓存中的 </span><span data-lake-id="ub05c2269" id="ub05c2269">Class</span><span data-lake-id="uf01d361b" id="uf01d361b"> 对象。</span></li>
   <li fid="uc65f7349" data-lake-id="u5777175b" id="u5777175b" data-lake-index-type="true"><strong><span data-lake-id="u39850a54" id="u39850a54">尝试使用Bootstrap类加载器加载</span></strong><span data-lake-id="u1344aeac" id="u1344aeac">： 尝试使用Bootstrap类加载器加载类，以防止Web应用程序覆盖Java SE类。如果加载成功，则返回加载的 Class 对象。</span></li>
   <li fid="uc65f7349" data-lake-id="ued580a09" id="ued580a09" data-lake-index-type="true"><strong><span data-lake-id="ua587711c" id="ua587711c">决定是否委派加载</span></strong><span data-lake-id="u21bc8201" id="u21bc8201">： 根据 delegate 属性和其他条件判断是否应该委派加载给父类加载器。</span></li>
   <li fid="uc65f7349" data-lake-id="u27818eab" id="u27818eab" data-lake-index-type="true"><strong><span data-lake-id="ue2d442f9" id="ue2d442f9">委派给父类加载器</span></strong><span data-lake-id="uf35c860f" id="uf35c860f">： 如果需要委派加载（delegate为true），尝试使用父类加载器加载类。</span></li>
   <li fid="uc65f7349" data-lake-id="ub8e3f0aa" id="ub8e3f0aa" data-lake-index-type="true"><strong><span data-lake-id="u20be5d70" id="u20be5d70">自己尝试加载</span></strong><span data-lake-id="u2aa00cb9" id="u2aa00cb9">： 如果未指定需要委派（delegate为false），或者未从父类加载器中找到类，则调用 findClass 方法尝试自己进行类加载。</span></li>
   <li fid="uc65f7349" data-lake-id="u7443444f" id="u7443444f" data-lake-index-type="true"><strong><span data-lake-id="u3fce10e8" id="u3fce10e8">委派给父类加载器</span></strong><span data-lake-id="u1565489a" id="u1565489a">： 如果未指定需要委派（delegate为false），且自己没加载到类，则尝试使用父类加载器加载类。</span></li>
  </ol>
  <p data-lake-id="u20f96e82" id="u20f96e82"><span data-lake-id="u4af244af" id="u4af244af">​</span><br></p>
  <p data-lake-id="ude1bbeba" id="ude1bbeba"><span data-lake-id="u742db6e8" id="u742db6e8">​</span><br></p>
  <p data-lake-id="u1000c5a9" id="u1000c5a9"><img src="https://cdn.nlark.com/yuque/0/2023/png/5378072/1702559818249-905f973a-4a20-4ee2-b603-688e0c460fd8.png?x-oss-process=image%2Fwatermark%2Ctype_d3F5LW1pY3JvaGVp%2Csize_29%2Ctext_SmF2YSA4IEd1IFA%3D%2Ccolor_FFFFFF%2Cshadow_50%2Ct_80%2Cg_se%2Cx_10%2Cy_10"></p>
  <p data-lake-id="u801017bf" id="u801017bf" style="text-align: center"><span data-lake-id="u8853b869" id="u8853b869">（图中红线和绿线是2选一分别执行的，不会同时执行，也不会交叉执行。）</span></p>
  <p data-lake-id="ueb0f7730" id="ueb0f7730"><br></p>
  <p data-lake-id="u35621d9b" id="u35621d9b"><span data-lake-id="ua569e636" id="ua569e636">以上，就是Tomcat的类加载机制。你说他打破双亲委派了么？</span></p>
  <p data-lake-id="uc1f5d7d2" id="uc1f5d7d2"><span data-lake-id="u4fff5a01" id="u4fff5a01">​</span><br></p>
  <p data-lake-id="ub6e2e1f1" id="ub6e2e1f1"><span data-lake-id="ub4dfd6c2" id="ub4dfd6c2">打破了，当delegate = false的时候，打破了双亲委派。但是也并不是上来就自己直接加载，而是也得先给老大哥——BootStrap尝试加载，避免JRE中的类被覆盖。</span></p>
  <p data-lake-id="ue31ccd6d" id="ue31ccd6d"><span data-lake-id="ud1ff2366" id="ud1ff2366">​</span><br></p>
  <p data-lake-id="u7f8333bd" id="u7f8333bd"><span data-lake-id="u762ac139" id="u762ac139">没打破，当delegate = true的时候，他是严格的遵守了双亲委派的。</span></p>
  <p data-lake-id="udd0a5fbe" id="udd0a5fbe"><span data-lake-id="u655b7ba1" id="u655b7ba1">​</span><br></p>
  <h1 data-lake-id="WUqf9" id="WUqf9"><span data-lake-id="uc3cef9fa" id="uc3cef9fa">扩展知识</span></h1>
  <p data-lake-id="ud10ab504" id="ud10ab504"><br></p>
  <h2 data-lake-id="CcOkL" id="CcOkL"><span data-lake-id="ua195bb0b" id="ua195bb0b">为什么破坏双亲委派</span></h2>
  <p data-lake-id="ub69a501a" id="ub69a501a"><br></p>
  <p data-lake-id="u0d55eec9" id="u0d55eec9"><span data-lake-id="u28c64fbd" id="u28c64fbd">一个Tomcat，是可以同时运行多个应用的，而不同的应用可能会同时依赖一些相同的类库，但是他们使用的版本可能是不一样的，但是这些类库中的Class的全路径名因为是一样的，如果都采用双亲委派的机制的话，是无法重复加载同一个类的，那么就会导致版本冲突。</span></p>
  <p data-lake-id="u6265bd98" id="u6265bd98"><span data-lake-id="u215883aa" id="u215883aa">​</span><br></p>
  <p data-lake-id="uda42ef58" id="uda42ef58"><span data-lake-id="u962c9c5e" id="u962c9c5e">而为了有更好的隔离性，所以在Tomcat中，每个应用都由一个独立的WebappClassLoader进行加载，这样就可以完全隔离开。而多个WebAppClassLoader之间是没有委派关系的，他们就是各自加载各自需要加载的Jar包。</span></p>
  <p data-lake-id="u9a7c647a" id="u9a7c647a"><strong><span data-lake-id="u65f96158" id="u65f96158">​</span></strong><br></p>
  <p data-lake-id="u501011d9" id="u501011d9"><span data-lake-id="u85e56b97" id="u85e56b97">由于每个Web应用程序都有自己的类加载器，因此不同Web应用程序中的类可以使用相同的类名，而不会产生命名冲突。</span></p>
  <p data-lake-id="uc481d3c3" id="uc481d3c3"><strong><span data-lake-id="ud19764ea" id="ud19764ea">​</span></strong><br></p>
  <p data-lake-id="u035683a1" id="u035683a1"><span data-lake-id="u201366cb" id="u201366cb">同时，由于每个Web应用程序都有自己的类加载器，因此在卸载一个Web应用程序时，它的所有类都会从内存中清除，这可以避免内存泄漏的问题。</span></p>
  <p data-lake-id="u301d9a72" id="u301d9a72"><span data-lake-id="u957f764a" id="u957f764a">​</span><br></p>
  <p data-lake-id="u8778f9b7" id="u8778f9b7"><span data-lake-id="u1ed1581a" id="u1ed1581a">这种层次化的类加载器结构和委派机制确保了类的唯一性和隔离性，避免了类的重复加载和冲突，同时也实现了多个Web应用程序的隔离和独立运行。</span></p>
  <p data-lake-id="u577fbe41" id="u577fbe41"><span data-lake-id="ue18898f1" id="ue18898f1">​</span><br></p>
  <p data-lake-id="u6d11c76a" id="u6d11c76a"><span data-lake-id="ua1f4c5da" id="ua1f4c5da">​</span><br></p>
  <h3 data-lake-id="k9kxX" id="k9kxX"><span data-lake-id="u1a3e0715" id="u1a3e0715">如何避免重复加载</span></h3>
  <p data-lake-id="ud067a2fd" id="ud067a2fd"><br></p>
  <p data-lake-id="u01d4a0b7" id="u01d4a0b7"><span data-lake-id="u59ca8512" id="u59ca8512">因为每个应用都是用WebAppClassLoader独自加载的，但是如果有一个公共的jar包，比如Spring，各个应用的版本都一样，那么岂不是要重复加载很多次了？这不是浪费么？</span></p>
  <p data-lake-id="uba9260f2" id="uba9260f2"><span data-lake-id="uf15159fc" id="uf15159fc">​</span><br></p>
  <p data-lake-id="u82b810e2" id="u82b810e2"><span data-lake-id="u5841ce17" id="u5841ce17">Tomcat给了个方案，那就是SharedClassLoader，我们可以把可以指定一个目录，让SharedClassLoader来加载，他加载的类在各个APP中都是可以共享使用的。</span></p>
 </body>
</html>